/* Author: David McCoy dave.mccoy@cospandesign.com
 *
 *     This file is part of DLCM\SubServ.
 *
 *  DLCM\SubServ is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  DLCM\SubServ is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with DLCM\SubServ.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/* PubSubManager.java
 *
 * Manages local pubsub, and foriegn manifestations of pubsubs
 *
 */

package pubsub;

import com.cospandesign.gui.pubsub.widgets.*;
import dlcm.DLCMListener;
import dlcm.Member;
import dlcm.Structure;
import dlcm.StructureManager;
import dlcm.DLCM;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import lcmtypes.control_t;

public class PubSubManager implements DLCMListener{
    //LCM mLCM;
    DLCM mDLCM;
    StructureManager mStructureManager;
    HashMap <String, PubSub> PubSubMap;
    HashMap <String, ArrayList<Widget>> InWidgetMap;
    HashMap <String, ArrayList<Widget>> OutWidgetMap;
    HashMap <String, Class> WidgetReferenceMap;
    ArrayList <String> UnparsedChannelStrings;
    ArrayList<PubSubManagerListener> mPubSubManagerListeners;


    PubSubManager (DLCM dlcm, StructureManager structureManager){
        //mLCM = lcm;
        mDLCM = dlcm;
        mStructureManager = structureManager;
        mPubSubManagerListeners = new ArrayList<PubSubManagerListener>();
        PubSubMap = new HashMap<String, PubSub>();
        SetupWidgetReferenceMap();
        mDLCM.addDLCMListener(this);

        InWidgetMap = new HashMap<String, ArrayList<Widget>>();
        OutWidgetMap = new HashMap<String, ArrayList<Widget>>();
        UnparsedChannelStrings = new ArrayList<String>();
    }

    private void SetupWidgetReferenceMap (){
        WidgetReferenceMap = new HashMap<String, Class>();

        String widgetName = null;
        widgetName = TextWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, TextWidget.class);

        widgetName = CheckBoxWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, CheckBoxWidget.class);

        widgetName = SliderWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, SliderWidget.class);

        widgetName = PaintWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, PaintWidget.class);

        widgetName = GraphWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, GraphWidget.class);

        widgetName = SinewaveWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, SinewaveWidget.class);
        
        widgetName = FunctionWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, FunctionWidget.class);

        widgetName = JEvalFunctionWidget.GetWidgetName();
        WidgetReferenceMap.put(widgetName, JEvalFunctionWidget.class);

    }

    public void StructuresChanged(){
        //Analyze all PubSub's and see if there is something different, if so,
        //fix the connections, and structures

        //compare each pubsub to see if they share the same structure ID, if so,
        //we need to delete all the forign channel associate with the changed
        //structure
    }

    // <editor-fold defaultstate="collapsed" desc="Implemented Functions">
    public void DLCMChannelAdded(String channelName) {
        //PubSubProvider Should handle this
    }

    public void DLCMChannelRemoved(String channelName) {
        //PubSubProvider Should handle this
    }

    public void DLCMMessageReceived(String channelName, Structure structure) {
        //Need to broadcast to all widgets
        if (OutWidgetMap.containsKey(channelName)){
            ArrayList<Widget> widgets = OutWidgetMap.get(channelName);
            for (Widget widget : widgets){
                widget.ReceiveMessage(structure);
            }
        }
    }

    public void DLCMError(String error, String channel) {
        //PubSubProvider Should handle this
    }

    public void PublishStructure(String channelName, Structure structure){
        mDLCM.publish(channelName, structure);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Getters for lists">
    public Object[] getPubSubNamesListData(){
        ArrayList<String> PubSubNames = new ArrayList<String>();

        for (String name : PubSubMap.keySet()){
            PubSubNames.add(name);
        }

        Object[] names = new Object[PubSubNames.size()];
        names = PubSubNames.toArray();
        return names;
    }
    public Object[] getChannelsListData(String pubSubName){
        PubSub psi = PubSubMap.get(pubSubName);
        ArrayList<String> Channels = new ArrayList<String>();

        for (String cName : psi.GetChannelNames(pubSubName)){
                    Channels.add(cName);
            }
            Object[] channels = new Object[Channels.size()];
            channels = Channels.toArray();
            return channels;
    }
    public Object[] getMembersListData (String pubSubName, String channelName){
        if (mDLCM.containsChannel(channelName)){
            ArrayList<Member> members = mDLCM.getChannelStructure(channelName).getMemberList();
            ArrayList<String> memberNames = new ArrayList<String>();
            for (Member member : members){
                memberNames.add(member.Name);
            }
            Object[] nameObjects = new Object[memberNames.size()];
            nameObjects = memberNames.toArray();
            return nameObjects;
        }
        //TODO Check Static Member
        Object [] debugObs = new Object[0];
        if (!PubSubMap.containsKey(pubSubName)){
            return debugObs;
        }
        Object staticObject = PubSubMap.get(pubSubName).getStaticChannel(channelName);

        Class c = staticObject.getClass();
        Field[] fs = c.getFields();
        ArrayList<String> FieldNames = new ArrayList<String>();
        for (Field f : fs){
            FieldNames.add(f.getName());
        }
        Object[] os = new Object[FieldNames.size()];
        os = FieldNames.toArray();
        return os;
    }
    public Object[] getWidgetsListData (String pubSubName, String channelName, String memberName) throws Exception{
        //Need to get compatable objects for the widget list
        Structure structure = mDLCM.getChannelStructure(channelName);
        structure.initialize(mDLCM);
        Member member = structure.getMember(memberName);



        ArrayList <String> widgetNames = new ArrayList<String>();
        //TODO find a better to see if a class will work!
        //Terrible way to do this, gotta find a better way.
            //Abstract methods can't be static
            //might be able to use reflections and get a function by name??
        for (String widgetName : WidgetReferenceMap.keySet()){
            Widget widget = null;
            try {
                widget = getWidgetChannelFromName(widgetName);
            } catch (Exception ex) {
                throw new Exception ("Dynamic Widget Instantiation Error "+ ex.getMessage());
            }
            if (widget.CanUseMember(member)){
                widgetNames.add(widgetName);
            }

        }

        Object[] nameObjects = new Object[widgetNames.size()];
        nameObjects = widgetNames.toArray();

        return nameObjects;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Listener">
    public void addPubSubManagerListener(PubSubManagerListener psml){
        mPubSubManagerListeners.add(psml);
    }
    public void removePubSubManagerListeners(PubSubManagerListener psml){
        mPubSubManagerListeners.remove(psml);
    }
    public void broadcastAddedPubSub(String pubsubName){
        for (PubSubManagerListener psml : mPubSubManagerListeners){
            psml.pubsubAdded(pubsubName);
        }
    }
    public void broadcastRemovedPubSub(String pubsubName){
        for (PubSubManagerListener psml : mPubSubManagerListeners){
            psml.pubsubRemoved(pubsubName);
        }
    }
    
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Widget Functions">
    public void addOutputWidget(String pubSub, String channelName, String member, String widgetName) throws Exception{
        if (!this.WidgetReferenceMap.containsKey(widgetName)){
            throw new Exception ("Widget Not Found");
        }

        Structure structure = mDLCM.getChannelStructure(channelName);
        structure.initialize(mDLCM);
        if (structure == null){
            throw new Exception ("Static structures not supported yet");
        }

        Widget widget = getWidgetChannelFromName(widgetName);
        if (!widget.CanUseMember(structure.getMember(member))){
            throw new Exception ("Unsuported Data Type: Member Name = " + member + " Widget Name = " + widgetName);
        }
        widget.SetupWidget(channelName, member, structure, this);


        ArrayList<Widget> widgetList = null;
        if (!OutWidgetMap.containsKey(channelName)){
            widgetList = new ArrayList<Widget>();
            OutWidgetMap.put(channelName, widgetList);
        }

        widgetList = OutWidgetMap.get(channelName);
        widgetList.add(widget);
    }
    public void removeOutputWidget (String channelName){
        if (OutWidgetMap.containsKey(channelName)){
            OutWidgetMap.remove(channelName);
        }

    }
    public void addInputWidget (String pubSub, String channelName, String member, String widgetName) throws Exception{
        if (!this.WidgetReferenceMap.containsKey(widgetName)){
            throw new Exception ("Widget Not Found");
        }

        Structure structure = mDLCM.getChannelStructure(channelName);
        structure.initialize(mDLCM);
        if (structure == null){
            throw new Exception ("Static structures not supported yet");
        }

        Class widgetClass = WidgetReferenceMap.get(widgetName);
        Widget widget = (Widget) widgetClass.newInstance();
        if (!widget.CanUseMember(structure.getMember(member))){
            throw new Exception ("Unsuported Data Type: Member Name = " + member + " Widget Name = " + widgetName);
        }
        widget.SetupWidget(channelName, member, structure, this);

        ArrayList<Widget> widgetList = null;
        if (!InWidgetMap.containsKey(channelName)){
            widgetList = new ArrayList<Widget>();
            InWidgetMap.put(channelName, widgetList);
        }
        widgetList = InWidgetMap.get(channelName);
        widgetList.add(widget);
    }
    public void removeInputWidget (String channelName){
        if (InWidgetMap.containsKey(channelName)){
            InWidgetMap.remove(channelName);
        }
    }
    public void addInputOutputWidget (String pubSub, String channelName, String member, String widgetName) throws Exception {
        if (!this.WidgetReferenceMap.containsKey(widgetName)){
            throw new Exception ("Widget Not Found");
        }

        Structure structure = mDLCM.getChannelStructure(channelName);
        structure.initialize(mDLCM);
        if (structure == null){
            throw new Exception ("Static structures not supported yet");
        }

        Widget widget = getWidgetChannelFromName(widgetName);
        if (!widget.CanUseMember(structure.getMember(member))){
            throw new Exception ("Unsuported Data Type: Member Name = " + member + " Widget Name = " + widgetName);
        }
        widget.SetupWidget(channelName, member, structure, this);


        ArrayList<Widget> widgetList = null;
        if (!OutWidgetMap.containsKey(channelName)){
            widgetList = new ArrayList<Widget>();
            OutWidgetMap.put(channelName, widgetList);
        }

        widgetList = OutWidgetMap.get(channelName);
        widgetList.add(widget);
        
        if (!InWidgetMap.containsKey(channelName)){
            widgetList = new ArrayList<Widget>();
            InWidgetMap.put(channelName, widgetList);
        }
        widgetList = InWidgetMap.get(channelName);
        widgetList.add(widget);
    }
    public void removeInputOutputWidget (String channelName){
        if (InWidgetMap.containsKey(channelName)){
            InWidgetMap.remove(channelName);
        }
        if (OutWidgetMap.containsKey(channelName)){
            OutWidgetMap.remove(channelName);
        }
    }
    private Widget getWidgetChannelFromName(String widgetName) throws InstantiationException, IllegalAccessException{
        Class widgetClass = WidgetReferenceMap.get(widgetName);
        return (Widget) widgetClass.newInstance();
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="PubSub Boiler Plate Functions">
    public boolean contains(String pubSubName){
        return PubSubMap.containsKey(pubSubName);
    }
    public PubSub getPubSub(String pubSubName){
        return PubSubMap.get(pubSubName);
    }
    public void AddPubSub (String pubSubName, Long boardID){

        if (PubSubMap.containsKey(pubSubName)){
            return;
        }
        PubSub psi = new PubSub(pubSubName, boardID, mDLCM);
        PubSubMap.put(pubSubName, psi);
        broadcastAddedPubSub(pubSubName);
    }
    public void RemovePubSub (String pubSubName){
        if (!PubSubMap.containsKey(pubSubName)){
            return;
        }
        PubSubMap.remove(pubSubName);
        broadcastRemovedPubSub(pubSubName);
    }
    public Set<String> getPubSubNames (){
        return PubSubMap.keySet();
    }


    // </editor-fold>
}
